//
// invisible="context.get['company_hide', True]"
// invisible: { context_company_hide: { default: true } }
// invisible: "context.get['company_hide', True]"
//   invisible: "context.get['hide_location', False]"
// invisible: { context_hide_location: { default: false } }
//
// invisible: { editable: true }

function check_equ_list(v1, v2) {
  if (v1.length === 0 && v2.length === 0) {
    return true
  } else if (v1.length || v2.length) {
    return false
  } else if (v1.length !== v2.length) {
    return false
  } else {
    return v1.reduce((acc, cur) => {
      acc = acc && v2.includes(cur)
      return acc
    }, true)
  }
}

function EQ(fv, val) {
  if (['string', 'number'].includes(typeof val)) {
    return fv === val
  } else if (typeof val === 'boolean') {
    const fv1 = fv ? true : false
    return val ? fv1 : !fv1
  } else if (Array.isArray(val)) {
    return check_equ_list(fv, val)
  } else {
    return fv === val
  }
}

function NE(fv, val) {
  if (['string', 'number'].includes(typeof val)) {
    return fv !== val
  } else if (typeof val === 'boolean') {
    const fv1 = fv ? true : false
    return val ? !fv1 : fv1
  } else if (Array.isArray(val)) {
    return !check_equ_list(fv, val)
  } else {
    return fv !== val
  }
}

function GT(fv, val) {
  return fv > val
}

function GE(fv, val) {
  return fv >= val
}

function LT(fv, val) {
  return fv < val
}

function LE(fv, val) {
  return fv <= val
}

function IN(fv, val) {
  return val.includes(fv)
}

function NIN(fv, val) {
  return !val.includes(fv)
}

function make_expression(exp, { root, ...kws }) {
  const { editable, record, context } = kws
  // console.log('exp', exp, kws)
  const [fname, op, val] = exp

  const op_map = {
    '=': EQ,
    '!=': NE,
    '>': GT,
    '>=': GE,
    '<': LT,
    '<=': LE,
    in: IN,
    'not in': NIN
  }

  function get_1st() {
    if (typeof fname === 'string') {
      if (fname.slice(0, 7) === 'parent.') {
        return record.parent[fname.slice(7)]
      } else {
        return record[fname]
      }
    } else if (!fname) {
      console.log('error, make_expression, ', exp)
      throw 'error,make_expression: ' + JSON.stringify(exp)
    } else if (typeof fname === 'object') {
      const ok = make_eval_object(fname, { ...kws })
      return ok
    } else {
      console.log('error, make_expression, ', exp, fname)
      throw 'error,make_expression: ' + JSON.stringify(exp)
    }
  }

  const fv = get_1st()
  // console.log('exp1', exp, fv, op, val)
  const ok = op_map[op](fv, val)
  // console.log('exp2', ok)
  return ok
}

function make_eval_array_one(modifiers, { root, ...kws }) {
  let todo = [...modifiers]

  const [first, ...next] = todo

  if (typeof first === 'string') {
    // console.log('todo string:', first, next)
    if (first === '&') {
      if (next.length === 0) {
        throw 'error and:' + JSON.stringify(root)
      }
      const [ok1, next1] = make_eval_array_one(next, { root, ...kws })
      if (next1.length === 0) {
        throw 'error and:' + JSON.stringify(root)
      }
      const [ok2, next2] = make_eval_array_one(next1, { root, ...kws })
      return [ok1 && ok2, next2]
    } else if (first === '|') {
      if (next.length === 0) {
        throw 'error and:' + JSON.stringify(root)
      }
      const [ok1, next1] = make_eval_array_one(next, { root, ...kws })
      if (next1.length === 0) {
        throw 'error and:' + JSON.stringify(root)
      }
      const [ok2, next2] = make_eval_array_one(next1, { root, ...kws })
      return [ok1 || ok2, next2]
    } else if (first === '!') {
      if (next.length === 0) {
        throw 'error and:' + JSON.stringify(root)
      }
      const [ok1, next1] = make_eval_array_one(next, { root, ...kws })
      return [!ok1, next1]
    } else {
      // console.log('error uknow:', root)
      throw 'error uknow:' + JSON.stringify(root)
    }
  } else if (Array.isArray(first)) {
    const ok = make_expression(first, { root, ...kws })
    return [ok, next]
  } else if (typeof first === 'boolean') {
    return [first, next]
  } else if (!first) {
    console.log('error: make_eval_array_one', modifiers, kws)
    throw 'error:' + JSON.stringify(root)
  } else if (typeof first === 'object') {
    // console.log('make_eval_array_one, object ', modifiers, kws)
    const ok = make_eval_object(first, { root: modifiers, ...kws })
    return [ok, next]
    // console.log(' make_eval_array_one object', res)
    // throw 'error:' + JSON.stringify(root)
  } else {
    throw 'error object:' + JSON.stringify(root)
  }
}

function make_eval_array(modifiers, { root, ...kws }) {
  if (!modifiers.length) {
    return false
  }
  // console.log('make_eval_array', modifiers, kws)
  const [ok, next] = make_eval_array_one(modifiers, { root, ...kws })
  // console.log('make_eval_array2', ok, next)
  let result = ok
  let todo = [...next]

  let cnt = 0
  while (todo.length && cnt < 100) {
    const [ok2, next2] = make_eval_array_one(todo, { root, ...kws })
    result = result && ok2
    todo = [...next2]
    cnt += 1
  }

  if (cnt > 90) {
    throw 'error' + JSON.stringify(modifiers)
  }

  // console.log(modifiers, kws)
  return result
}

function make_eval_object(modifiers, { root, ...kws }) {
  const keys = Object.keys(modifiers)

  if (keys.length !== 1) {
    // console.log('make_eval_object:', keys, modifiers)
    throw 'error make_eval_object' + JSON.stringify(modifiers)
  }

  const key = keys[0]
  const val = modifiers[key]

  function get_keys(mykey) {
    const ks = mykey.split('_')
    const k1 = ks[0]
    const k2 = ks.splice(1).join('_')
    return [k1, k2]
  }

  const [key1, key2] = get_keys(key)

  if (key1 === 'context') {
    const { context } = kws
    const res = key2 in context ? context[key2] : val
    // console.log('make_eval_object1:', key2, val, res, kws)
    return res
  } else if (key1 === 'editable') {
    const { editable } = kws
    return editable ? val : !val
  } else if (key1 === 'field') {
    // console.log('make_eval_object field:', key1, key2, val, kws, modifiers)
    const { record } = kws
    return record[key2]
    // throw 'error config' + JSON.stringify(modifiers)
  } else if (key1 === 'session') {
    const { env } = kws
    if (env.session[key2]) {
      return env.session[key2]
    } else {
      console.log('make_eval_object', val)
      throw 'error'
    }
  } else {
    console.log('make_eval_object:', key1, modifiers)
    throw 'error make_eval_object' + JSON.stringify(modifiers)
  }
}

export const eval_helper = {
  eval_array(modifiers, kws = {}) {
    // console.log('eval_array:', modifiers, kws)
    const res = make_eval_array(modifiers, { root: modifiers, ...kws })
    // console.log('eval_array2:', res)
    return res
  },
  eval_object(modifiers, kws = {}) {
    // console.log('init:', modifiers, kws)
    const res = make_eval_object(modifiers, { root: modifiers, ...kws })
    // console.log('init2:', res)
    return res
  }
}
